/**
 * 
 */
package cz.cuni.mff.abacs.burglar.logics.objects.agents;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import cz.cuni.mff.abacs.burglar.logics.objects.BaseInterface;
import cz.cuni.mff.abacs.burglar.logics.objects.Room;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Inventory;
import cz.cuni.mff.abacs.burglar.logics.objects.items.Item;
import cz.cuni.mff.abacs.burglar.logics.objects.positions.Position;


/**
 * Representation of a knowledge base of an agent.
 * 
 * @author abacs
 *
 */
public class BeliefBase {
	
	
	/** Remembered positions from a distance.
	 * 
	 * Changeable property. */
	protected Map<Integer, Position> _positionsFar = 
		new HashMap<Integer, Position>();
	
	/** Remembered positions after examination.
	 * 
	 * Changeable property. */
	protected Map<Integer, Position> _positionsNear = 
		new HashMap<Integer, Position>();
	
	/** IDs of the containers where the known items are.
	 * 
	 * Changeable property. */
	protected Map<Integer, Integer> _itemLocations = 
		new HashMap<Integer, Integer>();
	
	/** Remembered rooms.
	 * 
	 * Changeable property. */
	protected List<Room> _rooms = new ArrayList<Room>();
	
	
	// -------------------------------------------------------------------------
	
	
	/**
	 * 
	 */
	public void seenFromFar(List<Position> positions) {
		for(Position pos : positions){
			// add the knowledge about the position if it did not exist so far:
			if(this._positionsNear.containsKey(pos.getId()) == false &&
			   this._positionsFar.containsKey(pos.getId()) == false){
				this._positionsFar.put(pos.getId(), pos.examineFromFar());
			}
		}
	}
	
	
	/**
	 * 
	 */
	public void seenFromNear(List<Position> positions) {
		for(Position pos : positions){
			// add the knowledge about the position if it did not exist so far:
			this.seenFromNear(pos);
		}
	}
	
	
	/**
	 * 
	 */
	public void seenFromNear(Position position) {
		int id = position.getId();
		if(this._positionsFar.containsKey(id))
			this._positionsFar.remove(id);
		if(this._positionsNear.containsKey(id))
			this._positionsNear.remove(id);
		this._positionsNear.put(id, position);
		
		// update item locations if it's a container:
		if(position.isTypeOf(BaseInterface.Type.CONTAINER)){
			for(Item item : ((Inventory)position).getItems()){
				this._itemLocations.remove(item.getId());
				this._itemLocations.put(item.getId(), position.getId());
			}
		}
	}
	
	
	/**
	 * 
	 * @param item
	 * @param position
	 */
	public void addItemPosition(Item item, Position position) {
		this._itemLocations.put(item.getId(), position.getId());
	}
	
	
	/**
	 * 
	 * @param itemId
	 * @param position
	 */
	public void forgetItemPosition(int itemId) {
		this._itemLocations.remove(itemId);
	}
	
	
	/**
	 * 
	 * @param item
	 * @param position
	 */
	public void forgetItemPosition(Item item) {
		this.forgetItemPosition(item.getId());
	}
	
	
	// -------------------------------------------------------------------------
	
	
	/** Returns all the positions the agent knows about in their believed state.
	 * It includes all items, seen near or far. */
	public List<Position> getKnownPositions() {
		List<Position> ret = new ArrayList<Position>();
		ret.addAll(this._positionsFar.values());
		ret.addAll(this._positionsNear.values());
		return ret;
	}
	
	
	/** Returns null if nothing found.
	 * 
	 *  */
	public Integer getItemPositionId(int itemId) {
		if(this._itemLocations.containsKey(itemId)){
			return this._itemLocations.get(itemId);
		}
		return null;
	}
	
	
	/** Returns null if nothing found.
	 * 
	 *  */
	public Integer getItemPositionId(Item item) {
		return this.getItemPositionId(item.getId());
	}
	
	
	/**
	 * 
	 * @param positionId
	 * @return
	 */
	public boolean knowsPosition(int positionId) {
		return
			this._positionsFar.containsKey(positionId) ||
			this._positionsNear.containsKey(positionId);
	}
	
	
	/**
	 * 
	 * @param position
	 * @return
	 */
	public boolean knowsPosition(Position position) {
		return this.knowsPosition(position.getId());
	}
	
	
	/**
	 * 
	 * @param itemId
	 * @return
	 */
	public boolean knowsItemLocation(int itemId) {
		return this._itemLocations.containsKey(itemId);
	}
	
	
	/**
	 * 
	 * @param item
	 * @return
	 */
	public boolean knowsItemLocation(Item item) {
		return this.knowsItemLocation(item.getId());
	}
	
	
	/**
	 * Returns wether the position's details matches.
	 * 
	 * @param position
	 * @return
	 */
	public boolean matches(Position position) {
		Position own = this._positionsFar.get(position.getId());
		if(own != null)
			return own.matches(position);
		
		own = this._positionsNear.get(position.getId());
		if(own != null)
			return own.matches(position);
		
		return false;
	}
	
	
	/**
	 * Does not change the map!
	 * 
	 * @param original
	 */
	public void copyChangeables(BeliefBase original) {
		
		for(Position pos : original._positionsFar.values())
			this._positionsFar.put(pos.getId(), pos);
		
		for(Position pos : original._positionsNear.values())
			this._positionsNear.put(pos.getId(), pos);
		
		for(Entry<Integer, Integer> pair : original._itemLocations.entrySet())
			this._itemLocations.put(pair.getKey(), pair.getValue());
		
		for(Room room : original._rooms)
			this._rooms.add(room);
	}
	
}
